//============================================================================
// Name        : Supernode.cpp
// Author      : zhou zhao
// Version     :
// Copyright   : Your copyright notice
// Description : EE450 Fall 2011 UNIX Socket Programming Project
//============================================================================

#include <iostream>
#include <vector>

#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>

#include "SuperRecord.h"

#define SUPER_PORT_SERVER "22410" //port number the supernode will listen from server
#define SUPER_PORT_CLIENT "34100" //port number the supernode will listen from client
#define BACKLOG 10
#define MAXLINE 100
#define MAXDATASIZE 100
using namespace std;

vector<SuperRecord*> loginDB;

//translate general socket address to IPv4 or IPv6 address
void* sockAddrTranslate(struct sockaddr* sockAddr){
	if(sockAddr->sa_family == AF_INET){
		return &(((struct sockaddr_in*)sockAddr)->sin_addr);
	}else{
		return &(((struct sockaddr_in6*)sockAddr)->sin6_addr);
	}
}

//translate general socket address to IPv4 or IPv6 port number
unsigned int sockPortTranslate(struct sockaddr* sockAddr){
	if(sockAddr->sa_family == AF_INET){
		return ((struct sockaddr_in*)sockAddr)->sin_port;
	}else{
		return ((struct sockaddr_in6*)sockAddr)->sin6_port;
	}
}

void constructSock(){
	int sockfd, rv;
	struct addrinfo hints, *superInfo, *superPtr;

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_DGRAM;

	for(unsigned int i=0; i<loginDB.size(); i++){
		if((rv = getaddrinfo(loginDB[i]->getIPAddr().c_str(), loginDB[i]->getPort().c_str(),
				&hints, &superInfo)) != 0){
			fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
			exit(1);
		}
		for(superPtr = superInfo; superPtr != NULL; superPtr = superPtr->ai_next){
			if((sockfd = socket(superPtr->ai_family, superPtr->ai_socktype,
					superPtr->ai_protocol)) == -1){
				perror("supernode: socket has error");
				continue;
			}
			break;
		}
		if(superPtr == NULL){
			perror("supernode: superPtr is NULL");
			exit(1);
		}
		loginDB[i]->setSockfd(sockfd);
		loginDB[i]->setAddrInfo(superPtr);
//		freeaddrinfo(superInfo);
	}
}

int main() {

	SuperRecord* recordPtr;

	int sockFD, sockFDNew;
	struct addrinfo hints, *superInfo, *superPtr;
	int returnValue, yes = 1;

	struct sockaddr_storage serverAddr, myAddr;
	socklen_t serverAddrSize, myAddrLen;
	char ipAddr[INET6_ADDRSTRLEN];
	int numByteRec, numByteSend;
	char data[MAXDATASIZE];

	char fileName1[] = "UserLogin.txt";
	char fileName2[] = "UserText.txt";
	FILE* fileHandle;
	char buffer[MAXLINE];
	char username[MAXLINE];
	char ipaddress[MAXLINE];
	char portnumber[MAXLINE];

	string target, message;
	fpos_t fpos;

	//prepare IP address info
	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_PASSIVE;

	//get IP address info
	if((returnValue = getaddrinfo(NULL, SUPER_PORT_SERVER, &hints, &superInfo)) != 0){
		fprintf(stderr, "getaddrinfo: %s.\n", gai_strerror(returnValue));
		exit(1);
	}

	for(superPtr = superInfo; superPtr != NULL; superPtr = superPtr->ai_next){
		//create a socket
		if((sockFD = socket(superPtr->ai_family, superPtr->ai_socktype,
				superPtr->ai_protocol)) == -1){
			perror("supernode: socket has error");
			continue;
		}

		//setup socket option
		if(setsockopt(sockFD, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1){
			perror("supernode: setsocketopt has error");
			exit(1);
		}

		//bind socket
		if(bind(sockFD, superPtr->ai_addr, superPtr->ai_addrlen) == -1){
			close(sockFD);
			perror("supernode: bind has error");
			continue;
		}
		break;
	}
	if(superPtr == NULL){
		perror("supernode: superPtr is NULL");
		exit(1);
	}
	struct hostent* host = gethostbyname("nunki.usc.edu");
	char* myIP = inet_ntoa(*(struct in_addr*)*host->h_addr_list);
	cout<<"Phase2: Supernode has TCP port "<<SUPER_PORT_SERVER<<" and IP address "<<myIP<<endl;
	freeaddrinfo(superInfo);

	if(listen(sockFD, BACKLOG) == -1){
		perror("supernode: listen has error");
		exit(1);
	}
//	cout<<"Phase2: Supernode is waiting for server connections ..."<<endl;

	serverAddrSize = sizeof(serverAddr);
	sockFDNew = accept(sockFD, (struct sockaddr*)&serverAddr, &serverAddrSize);
	if(sockFDNew == -1){
		perror("supernode: accept has error");
		exit(1);
	}

	inet_ntop(serverAddr.ss_family, sockAddrTranslate((struct sockaddr*)&serverAddr),
			ipAddr, sizeof(ipAddr));
//	cout<<"supernode: got server connection from "<<ipAddr<<endl;

	while(loginDB.size() != 3){
	if((numByteRec = recv(sockFDNew, data, MAXDATASIZE-1, 0)) == -1){
		perror("supernode: recv has error");
		exit(1);
	}
	data[numByteRec] = '\0';
//	cout<<data<<endl;

	//open user login file
	if((fileHandle = fopen(fileName1, "w+")) == NULL){
		perror("supernode: fopen has error");
		exit(1);
	}
	fgetpos(fileHandle, &fpos);
	if(fputs(data, fileHandle) == EOF){
		perror("supernode: fputs has error");
		exit(1);
	}
	fsetpos(fileHandle, &fpos);
//	cout<<"jump into while loop"<<endl;
	//construct login user DB
	while(fgets(buffer, MAXLINE, fileHandle) != NULL){
		if(sscanf(buffer, "%s %s %s", username, ipaddress, portnumber) == 3){
			recordPtr = new SuperRecord();
			recordPtr->setUsername(string(username));
			recordPtr->setIPAddr(string(ipaddress));
			recordPtr->setPort(string(portnumber));
			loginDB.push_back(recordPtr);
		}else{
			perror("supernode: user login format is not correct");
		}
	}
	fclose(fileHandle);
	}
	close(sockFD);
	close(sockFDNew);
	cout<<"Phase2: Supernode received "<<loginDB.size()<<" username/IP address pairs"<<endl;
//	cout<<"loginDB has sizeof "<<loginDB.size()<<endl;
	/*
	for(unsigned int i=0; i<loginDB.size(); i++){
			cout<<loginDB[i]->getUsername()<<" "<<loginDB[i]->getIPAddr()<<" "<<loginDB[i]->getPort()<<endl;
	}
	*/
	cout<<"End of Phase 2 for Supernode"<<endl;

	constructSock(); //construct socket structure
	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_DGRAM;
	hints.ai_flags = AI_PASSIVE;

	if((returnValue = getaddrinfo(NULL, SUPER_PORT_CLIENT, &hints, &superInfo)) != 0){
		fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(returnValue));
		exit(1);
	}

	for(superPtr = superInfo; superPtr != NULL; superPtr = superPtr->ai_next){
		if((sockFD = socket(superPtr->ai_family, superPtr->ai_socktype, superPtr->ai_protocol)) == -1){
			perror("supernode: socket has error");
			continue;
		}
		if(bind(sockFD, superPtr->ai_addr, superPtr->ai_addrlen) == -1){
			close(sockFD);
			perror("supernode: bind has error");
			continue;
		}
		break;
	}
	if(superPtr == NULL){
		perror("supernode: superPtr is NULL");
		exit(1);
	}
	freeaddrinfo(superInfo);
	cout<<"Phase3: Supernode has static UDP port of "<<SUPER_PORT_CLIENT<<" IP address of "<<myIP<<endl;
//	cout<<"Phase3: Supernode is waiting for client connections ..."<<endl;
	sleep(10);

	while(1){
		serverAddrSize = sizeof(serverAddr);
		if((numByteRec = recvfrom(sockFD, data, MAXDATASIZE-1, 0,
				(struct sockaddr*)&serverAddr, &serverAddrSize)) == -1){
			perror("supernode: recvfrom has error");
			exit(1);
		}
		data[numByteRec] = '\0';

		inet_ntop(serverAddr.ss_family, sockAddrTranslate((struct sockaddr*)&serverAddr),
				ipAddr, sizeof(ipAddr));
//		cout<<"supernode: got client connection from "<<ipAddr<<endl;
		cout<<"Phase3: Supernode received the message "<<data<<endl;

		if((fileHandle = fopen(fileName2, "w+")) == NULL){
			perror("supernode: fopen has error");
			exit(1);
		}
		fgetpos(fileHandle, &fpos);
		if(fputs(data, fileHandle) == EOF){
			perror("supernode: fputs has error");
			exit(1);
		}
		fsetpos(fileHandle, &fpos);
		while(fgets(buffer, MAXLINE, fileHandle) != NULL){
			target = string(buffer).substr(0, 6);
			message = string(buffer).erase(0, 7);
			for(unsigned int i=0; i<loginDB.size(); i++){
				if(loginDB[i]->getUsername().compare(target) == 0){
					if((numByteSend = sendto(loginDB[i]->getSockfd(), message.c_str(),
							message.size(), 0, loginDB[i]->getAddrInfo()->ai_addr,
							loginDB[i]->getAddrInfo()->ai_addrlen)) == -1){
						perror("supernode: sendto has error");
						exit(1);
					}
					if(getsockname(loginDB[i]->getSockfd(), (struct sockaddr*)&myAddr, (socklen_t*)&myAddrLen) == -1){
							perror("server getsockname has error");
							exit(1);
					}
					/*
					printf("Phase3: Supernode sent the message %s on dynamic UDP port number %d\n", message.c_str(),
							sockPortTranslate((sockaddr*)&myAddr));
							*/
					printf("Phase3: Supernode sent the message %s\n", message.c_str());
					break;
				}
			}
		}

		fclose(fileHandle);
	}
	cout<<"End of Phase 3 for Supernode"<<endl;
	return 0;
}
